<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
// +----------------------------------------------------------------------+
// | Treacle PHP Framework Version 0.10                                   |
// +----------------------------------------------------------------------+
// | Copyright (c) 2007 Treacle.cn All rights reserved.					  |
// +----------------------------------------------------------------------+
// | Licensed under the Apache License, Version 2.0 (the 'License');      |
// | you may not use this file except in compliance with the License.     |
// | You may obtain a copy of the License at                              |
// | http://www.apache.org/licenses/LICENSE-2.0                           |
// | Unless required by applicable law or agreed to in writing, software  |
// | distributed under the License is distributed on an 'AS IS' BASIS,    |
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      |
// | implied. See the License for the specific language governing         |
// | permissions and limitations under the License.                       |
// +----------------------------------------------------------------------+
// | Author: BoPo <ibopo@126.com>										  |
// +----------------------------------------------------------------------+
/**
 * Treacle Template Class file
 *
 * @author BoPo <ibopo@126.com>
 * @link http://www.treacle.cn/
 * @copyright Copyright &copy; 2007 Treacle
 * @license http://www.treacle.cn/license/
 * @version $Id $
 * @package libraries
 */
/**
 * Treacle Template Class
 *
 * @package libraries
 * @author BoPo <ibopo@126.com>
 * @since 0.10
 * @version	$Id $
 */
class Template
{
	/**
	 * 模版文件存放的路径
	 * @var string
	 * @access private
	 */
	private $template_dir = "views/";

	/**
	 * 模版编译的临时文件夹
	 * @var srting
	 */
	private $temp_dir = 'tmp/_views';
	/**
	 * 缓存周期
	 * @access public
	 * @var int
	 */
	private $compile_lifetime = 0;
	/**
	* 缓存文件(私有)
	* @access private
	*/
	private $tpl_file;
	/**
	 * 模版要处理的数组
	 * @var private
	 */
	private $data = array();
	/**
	 * 构造函数
	 */
	public function __construct()
	{
		$this->template_dir			= APPPATH.$this->template_dir;
		$this->temp_dir  			= APPPATH.$this->temp_dir;
		$this->compile_lifetime		= Configure::item('template_compile_lifetime');
		$this->data 				= Configure::item_array();
	}
	/**
	 * 修改模版文件
	 * @param string $template 模版文件名
	 */
	public function setTemplate($template)
	{
		$this->tpl_file  =  $template;
	}
	/**
	 * 安置模版内容
	 *
	 * Example:
	 * $template->assign( 'TITLE',     'My Document Title' );
	 * $template->assign( 'userlist',  array(
	 *                                 array( 'ID' => 123,  'NAME' => 'John Doe' ),
	 *                                 array( 'ID' => 124,  'NAME' => 'Jack Doe' ),
	 *                             );
	 * @access public
	 * @param string $name Parameter Name
	 * @param mixed $value Parameter Value
	 * @desc Assign Template Content
	 */
	public function assign ( $name, $value = '' )
	{
		if (is_array($name))
		{
			foreach ($name as $k => $v)
			{
				$this->data[$k]  =  $v;
			}
		}
		else
		{
			$this->data[$name]  =  $value;
		}
	}
	/**
	 * 追加模版内容
	 * <code>
	 * $template->append( 'userlist',  array( 'ID' => 123,  'NAME' => 'John Doe' ) );
	 * $template->append( 'userlist',  array( 'ID' => 124,  'NAME' => 'Jack Doe' ) );
	 * </code>
	 * @access public
	 * @param string $name Parameter Name
	 * @param mixed $value Parameter Value
	 * @desc Assign Template Content
	 */
	public function append ( $name, $value )
	{
		if (is_array($value))
		{
			$this->data[$name][]  =  $value;
		}
		elseif (!is_array($this->data[$name]))
		{
			$this->data[$name]  .=  $value;
		}
	}
	/**
	 * 输出html内容
	 * @access public
	 * @param array $_top Content Array
	 * @desc Execute parsed Template
	 */
	public function display ( $_top = '' , $unite=true)
	{
		if($_top!='')
		{
			if ($unite)
			{
				$_top = array_merge($_top,$this->data);
			}
			else
			{
				$_top['base_url']	= $this->data['base_url'];
				$_top['assets_url'] = $this->data['assets_url'];
			}

		}
		if (!is_array($_top))
		{
			$_top  =  $this->data;
		}
		// 判断模版文件是否为空
		if (!isset($this->tpl_file) || $this->tpl_file=='')
		{
			$exception=new TTemplateException('template_content_null');
			$exception->setTemplateFile($this->tpl_file);
			throw $exception;
		}
		//	Make sure that folder names have a trailing '/'
		if (strlen($this->template_dir)  &&  substr($this->template_dir, -1) != '/')
		{
			$this->template_dir  .=  '/';
		}
		if (strlen($this->temp_dir)  &&  substr($this->temp_dir, -1) != '/')
		{
			$this->temp_dir  .=  '/';
		}
		$_obj  		=  &$_top;
		$_stack_cnt =  0;
		$_stack[$_stack_cnt++]  =  $_obj;
		//	检查模版是否已经被编译
		$cpl_file = $this->temp_dir . md5(preg_replace('/[:\/.\\\\]/', '_', $this->tpl_file)) . EXT;
		$this->_compile($cpl_file);
		@include_once($cpl_file);
		unset ($_top);
		$this->data='';
	}
	/**
	 * Parser Wrapper
	 * Returns Template Output as a String
	 * @access public
	 * @param array $_top Content Array
	 * @return string  Parsed Template
	 * @desc Output Buffer Parser Wrapper
	*/
	public function result ( $_top = '' )
	{
		ob_start();
		$this->display( $_top );
		$result  =  ob_get_contents();
		ob_end_clean();
		return $result;
	}
	/**
	 * 返回文件的修改时间
	 * @access private
	 * @param string $filename
	 * @return mixed
	 * @desc Determine Last Filechange Date
	 */
	private function _mtime ( $filename )
	{
		if (is_file($filename))
		{
			return filemtime($filename);
		}
		return 0;
	}
	/**
	 * 包含文件的操作
	 * <!-- INCLUDE: inc.html -->
	 * @param 	string $inc_file (模版文件名)
	 * @return 	string (包含后整个的整个模版文件)
	 */
	private function _load($inc_file)
	{
		if (!is_file($inc_file) || ($str = file_get_contents($inc_file))===false)
		{
			$exception=new TTemplateException('template_content_null');
			$exception->setTemplateFile($inc_file);
			throw $exception;
		}
		// 必须严格匹配
		if (preg_match_all('/<!-- INCLUDE: ([a-zA-Z0-9\/_\.-]+) -->/', $str, $var))
		{
			foreach ($var[1] as $tag)
			{
				$tmpfile = $tag;
				if (substr($tag, 0, 1) != '/')
				{
					$tmpfile = $this->template_dir .  $tag;
				}
				if (!file_exists($tmpfile))
				{
					$line = 0;
					$lines = file($inc_file);
					foreach ($lines as $key=>$val)
					{
						if (ereg("<!-- INCLUDE: $tag -->",$val))
						{
							$line = $key+1;
							break;
						}
					}
					$exception=new TTemplateException('template_format_invalid');
					$exception->setLineNumber($line);
					$exception->setTemplateFile($inc_file);
					$exception->setTemplateSource($str);
					throw $exception;
				}
				$str2 = $this->_load($tmpfile);
				$str = str_replace("<!-- INCLUDE: $tag -->", $str2, $str);
			}
		}
		return $str;
	}
	/**
	 * 模版编译主函数
	 * @param string  Compiled Template Filename
	 * @desc Creates Compiled PHP Template
	 */
	private function _compile( $cpl_file )
	{
		$template = $this->_load($this->template_dir . $this->tpl_file);
		//	判断语句的编译
		$page  =  preg_replace("/<!-- ENDIF.+?-->/",  "<?php\n}\n?>",  $template);
		$page  =  preg_replace("/<!-- END[ a-zA-Z0-9_.]* -->/",  "<?php\n}\n\$_obj=\$_stack[--\$_stack_cnt];}\n?>",  $page);
		$page  =  str_replace ("<!-- ELSE -->",  "<?php\n} else {\n?>",  $page);
		//	循环的编译
		$var = "";
		if (preg_match_all('/<!-- BEGIN ([a-zA-Z0-9_.]+) -->/', $page, $var))
		{
			foreach ($var[1] as $tag)
			{
				list($parent, $block)  =  $this->_var_name($tag);
				$code  =  "<?php\n"
				. "if (!empty(\$$parent"."['$block'])){\n"
				. "if (!is_array(\$$parent"."['$block']))\n"
				. "\$$parent"."['$block']=array(array('$block'=>\$$parent"."['$block']));\n"
				. "\$_tmp_arr_keys=array_keys(\$$parent"."['$block']);\n"
				. "if (\$_tmp_arr_keys[0]!='0')\n"
				. "\$$parent"."['$block']=array(0=>\$$parent"."['$block']);\n"
				. "\$_stack[\$_stack_cnt++]=\$_obj;\n"
				. "foreach (\$$parent"."['$block'] as \$rowcnt=>\$$block) {\n"
				. "\$$block"."['ROWCNT']=\$rowcnt;\n"
				. "\$$block"."['ALTROW']=\$rowcnt%2;\n"
				. "\$$block"."['ROWBIT']=\$rowcnt%2;\n"
				. "\$$block"."['INDEX']=\$rowcnt+1;\n"
				. "\$_obj=&\$$block;\n?>";
				$page  =  str_replace("<!-- BEGIN $tag -->",  $code,  $page);
			}
		}
		//	判断的开头部分编译
		if (preg_match_all('/<!-- (ELSE)?IF ([a-zA-Z0-9_.]+)([!=<>]+)"([^"]*)" -->/', $page, $var))
		{
			foreach ($var[2] as $cnt => $tag)
			{
				list($parent,$block) = $this->_var_name($tag);
				$cmp   =  $var[3][$cnt];
				$val   =  $var[4][$cnt];
				$else  =  ($var[1][$cnt] == 'ELSE') ? '} else' : '';
				if ($cmp == '=')
				{
					$cmp  =  '==';
				}
				elseif($cmp == '<>')
				{
					$cmp  =  '!=';
				}
				$code  =  "<?php\n$else"."if (\$$parent"."['$block'] $cmp \"$val\"){\n?>";
				$page  =  str_replace($var[0][$cnt],  $code,  $page);
			}
		}
		//	ELSE标签的编译
		if (preg_match_all('/<!-- (ELSE)?IF ([a-zA-Z0-9_.]+) -->/', $page, $var))
		{
			foreach ($var[2] as $cnt => $tag)
			{
				$else  =  ($var[1][$cnt] == 'ELSE') ? '} else' : '';
				list($parent, $block)  =  $this->_var_name($tag);
				$code  =  "<?php\n$else"."if (!empty(\$$parent"."['$block'])){\n?>";
				$page  =  str_replace($var[0][$cnt],  $code,  $page);
			}
		}
		//	一般变量的编译{变量名}
		if (preg_match_all('/{([a-zA-Z0-9_. >]+)}/', $page, $var))
		{
			foreach ($var[1] as $fulltag)
			{
				//	Determin Command (echo / $obj[n]=)
				list($cmd, $tag)  		=  $this->_cmd_name($fulltag);
				list($block, $skalar)  	=  $this->_var_name($tag);

				$code  =  "<?php\n$cmd \$$block"."['$skalar'];\n?>\n";
				$page  =  str_replace('{'.$fulltag.'}',  $code,  $page);
			}
		}
		//	编译文件到缓存目录
		if (($this->_mtime($cpl_file)+$this->compile_lifetime) <= time())
		{
			@unlink($cpl_file);
			if (!file_put_contents($cpl_file,$page))
			{
				throw new TTemplateException('template_write_error');
			}
		}
		return true;
	}
	/**
	 * Splits Template-Style Variable Names into an Array-Name/Key-Name Components
	 * {example}               :  array( "_obj",                   "example" )  ->  $_obj['example']
	 * {example.value}         :  array( "_obj['example']",        "value" )    ->  $_obj['example']['value']
	 * {example.0.value}       :  array( "_obj['example'][0]",     "value" )    ->  $_obj['example'][0]['value']
	 * {top.example}           :  array( "_stack[0]",              "example" )  ->  $_stack[0]['example']
	 * {parent.example}        :  array( "_stack[$_stack_cnt-1]",  "example" )  ->  $_stack[$_stack_cnt-1]['example']
	 * {parent.parent.example} :  array( "_stack[$_stack_cnt-2]",  "example" )  ->  $_stack[$_stack_cnt-2]['example']
	 *
	 * @param string $tag Variale Name used in Template
	 * @return array  Array Name, Key Name
	 * @access private
	 * @desc Splits Template-Style Variable Names into an Array-Name/Key-Name Components
	 */
	private function _var_name($tag)
	{
		$parent_level  =  0;
		while (substr($tag, 0, 7) == 'parent.')
		{
			$tag  =  substr($tag, 7);
			$parent_level++;
		}
		if (substr($tag, 0, 4) == 'top.')
		{
			$obj  =  '_stack[0]';
			$tag  =  substr($tag,4);
		}
		elseif ($parent_level)
		{
			$obj  =  '_stack[$_stack_cnt-'.$parent_level.']';
		}
		else
		{
			$obj  =  '_obj';
		}
		while (is_int(strpos($tag, '.')))
		{
			list($parent, $tag)  =  explode('.', $tag, 2);

			if (is_numeric($parent))
			{
				$obj  .=  "[" . $parent . "]";
			}
			else
			{
				$obj  .=  "['" . $parent . "']";
			}
		}
		return array($obj, $tag);
	}
	/**
	 * Determine Template Command from Variable Name
	 * {variable}             :  array( "echo",              "variable" )  ->  echo $_obj['variable']
	 * {variable > new_name}  :  array( "_obj['new_name']=", "variable" )  ->  $_obj['new_name']= $_obj['variable']
	 *
	 * @param string $tag Variale Name used in Template
	 * @return array  Array Command, Variable
	 * @access private
	 * @desc Determine Template Command from Variable Name
	 */
	private function _cmd_name($tag)
	{
		$tagvar = "";
		if (preg_match('/^(.+) > ([a-zA-Z0-9_.]+)$/', $tag, $tagvar))
		{
			$tag  =  $tagvar[1];
			list($newblock, $newskalar)  =  $this->_var_name($tagvar[2]);
			$cmd  =  "\$$newblock"."['$newskalar']=";
		}
		else
		{
			$cmd  =  " echo ";
		}
		return array($cmd, $tag);
	}
}
/*
+--------------------------------------------------------------------------
|   Last Modified By BoPo
|   Date: Fri Nov 16 14:18:51 CST 2007
+--------------------------------------------------------------------------
*/
?>